Skip to content

fix(Dropdown): keep selected value inside input for searchable single select#3405

Open
rivka-ungar wants to merge 16 commits into
masterfrom
fix/single-searchable-dropdown-a11y-selected-value
Open

fix(Dropdown): keep selected value inside input for searchable single select#3405
rivka-ungar wants to merge 16 commits into
masterfrom
fix/single-searchable-dropdown-a11y-selected-value

Conversation

@rivka-ungar

@rivka-ungar rivka-ungar commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Problem

The searchable single-select combobox forced the input value to null after every selection and rendered the selected label as a visual overlay on top of an empty input.

Screen readers read what's inside the input — not the overlay. So a combobox with an active selection announced as "Edit combo, collapsed, blank" (JAWS). The user had no way to know what they'd selected.

This fails WCAG 2.1 SC 4.1.2 — Name, Role, Value (Level A), which requires the current value of a form control to be programmatically determinable.

Fix

Stop overriding Downshift's default so the selected item's label lives inside the input, where assistive technologies can read it. The selected value is no longer a separate visual layer.

Supporting changes keep the component's existing behavior intact:

  • initialInputValue seeded with the selected label, so a defaultValue/controlled value is visible on mount (previously the overlay handled this).
  • onInputValueChange filters the list only on real user typing (InputChange), ignoring the label Downshift writes into the input on selection/blur.
  • onIsOpenChange resets the filter when the menu closes, so reopening shows the full option list — matching the component's prior behavior and the documented combobox pattern (same approach Chakra v3 / Ark UI recommend: reset the filter on open).

The overlay in SingleSelectTrigger now renders only for non-searchable single select (where inputValue stays null), so that path is unaffected. Multi-select uses a separate hook and is untouched.

Behavior parity

Aligned with Chakra v3 (Ark UI / Zag.js):

Behavior Chakra v3 This PR
Selected label in input ✅ default
aria-selected + activedescendant on selected option at open ✅ (already)
Reopen shows all options ✅ (recommended reset())

Storybook

Adds a dedicated Components/Dropdown/Searchable single select page documenting all searchable single-select variants: overview, sizes, states, default/controlled value, icons & avatars, groups (sticky titles & dividers), tooltips, clearable / max-height, and custom filter / empty message. The default-value story demonstrates the selected value living inside the input.

Tests

  • All 71 Dropdown tests pass.
  • Rewrote 2 tests that asserted the old overlay behavior:
    • "faded selected item when focused" → now asserts the input holds the selected value.
    • "indent startElement not in selected value" → moved to searchable: false, since the indent-stripping overlay logic now applies only to non-searchable single select.

Test plan

  • Searchable single select: select an option → label shows in the input, no overlay; tab away and back → screen reader announces the selected value
  • Reopen after selecting → full option list shown, selected option marked/highlighted
  • Typing filters the list; clearing shows all
  • defaultValue / controlled value shows the label in the input on mount
  • Non-searchable single select and multi-select unchanged

🤖 Generated with Claude Code

… select

The searchable single-select combobox previously forced the input value to
null after selection and rendered the selected label as a visual overlay on
top of an empty input. Screen readers read the input, not the overlay, so a
selected combobox announced as "blank" — failing WCAG 2.1 SC 4.1.2 (Name,
Role, Value, Level A).

Stop overriding Downshift's default so the selected item's label lives inside
the input and is exposed to assistive technologies. Supporting changes keep
the component's existing behavior intact:

- Seed initialInputValue with the selected label so a defaultValue/value is
  visible on mount (previously handled by the overlay).
- Filter the list only on real user typing (InputChange); ignore the label
  Downshift writes into the input on selection/blur.
- Reset the filter when the menu closes so reopening shows the full list,
  matching the prior behavior and the documented combobox pattern.

The overlay in SingleSelectTrigger now naturally renders only for
non-searchable single select (where inputValue stays null), so that path is
unaffected. Multi-select uses a separate hook and is untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rivka-ungar rivka-ungar requested a review from a team as a code owner June 10, 2026 18:33
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (12) 📘 Rule violations (4)

Grey Divider


Action required

1. DropdownInput props typed inline 📘 Rule violation ⚙ Maintainability ⭐ New
Description
DropdownInput defines its props type inline in the component file and does not extend
VibeComponentProps. This breaks the repo’s required prop typing convention and makes the component
API harder to standardize and reuse.
Code

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[R9-19]

+const DropdownInput = ({
+  inputSize,
+  fullWidth,
+  onKeyDown: externalKeyDown,
+  inputRef: externalInputRef
+}: {
+  inputSize?: "small" | "medium" | "large";
+  fullWidth?: boolean;
+  onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
+  inputRef?: RefObject<HTMLInputElement>;
+}) => {
Evidence
PR Compliance ID 2 requires component prop interfaces to live in a dedicated *.types.ts file and
extend VibeComponentProps. The updated DropdownInput declares an inline object type for its
props directly in DropdownInput.tsx.

CLAUDE.md: Component props types must be defined in *.types.ts and extend VibeComponentProps
packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-19]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DropdownInput` has an inline props type in `DropdownInput.tsx` and it does not extend `VibeComponentProps`, which violates the component props typing convention.

## Issue Context
The PR adds new props (`onKeyDown`, `inputRef`) and types them inline, further expanding the component API without using the required `*.types.ts` pattern.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-19]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. MultiSelectedValuesProps typed inline 📘 Rule violation ⚙ Maintainability ⭐ New
Description
MultiSelectedValues keeps its props type (MultiSelectedValuesProps) defined inside the component
file and it does not extend VibeComponentProps. This violates the required component props typing
standard.
Code

packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[R17-24]

  disabled?: boolean;
  readOnly?: boolean;
  minVisibleCount?: number;
+  /** Extra props (tabIndex, onKeyDown, etc.) to spread on each visible chip container. */
+  getChipContainerProps?: (item: Item, index: number) => Record<string, any>;
+  /** Ref forwarded to the +N overflow Chips element, for external keyboard focus management. */
+  badgeRef?: React.Ref<HTMLDivElement>;
};
Evidence
PR Compliance ID 2 requires component prop interfaces to be declared in a *.types.ts file and
extend VibeComponentProps. MultiSelectedValuesProps is declared directly in
MultiSelectedValues.tsx as an inline type and does not extend VibeComponentProps.

CLAUDE.md: Component props types must be defined in *.types.ts and extend VibeComponentProps
packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[13-24]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MultiSelectedValuesProps` is defined inline in the component file and does not extend `VibeComponentProps`, contrary to the required typing convention.

## Issue Context
This PR expands the props surface area (e.g., `getChipContainerProps`, `badgeRef`) while keeping the props type local to the `.tsx` file.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[13-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. DropdownInput not forwardRef 📘 Rule violation ⚙ Maintainability ⭐ New
Description
DropdownInput is implemented as a plain function component and introduces an inputRef prop
instead of using React.forwardRef. This violates the required forwardRef component pattern and can
lead to inconsistent ref support across the design system.
Code

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[R9-19]

+const DropdownInput = ({
+  inputSize,
+  fullWidth,
+  onKeyDown: externalKeyDown,
+  inputRef: externalInputRef
+}: {
+  inputSize?: "small" | "medium" | "large";
+  fullWidth?: boolean;
+  onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
+  inputRef?: RefObject<HTMLInputElement>;
+}) => {
Evidence
PR Compliance ID 3 requires components to use the forwardRef pattern. The updated DropdownInput
is still a plain function component and adds an inputRef prop rather than forwarding a ref via
React.forwardRef.

CLAUDE.md: Components must use the React forwardRef pattern
packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-19]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DropdownInput` does not use `React.forwardRef` and instead adds an `inputRef` prop, which conflicts with the compliance requirement that components use the forwardRef pattern.

## Issue Context
The component now accepts `inputRef?: RefObject<HTMLInputElement>` and manually chooses between internal vs external refs, but it still doesn’t expose a standard `ref` to consumers.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-19]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (6)
4. Backspace chip-nav missing 🐞 Bug ≡ Correctness ⭐ New
Description
The interactiveChips prop documentation promises that pressing Backspace from the input moves
focus to the last chip, but the implementation only handles ArrowLeft and never handles
Backspace. As a result, keyboard-only users can’t perform the documented navigation in
interactiveChips mode.
Code

packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[R46-57]

+        <div
+          className={styles.multiWrapper}
+          onKeyDown={e => {
+            if (
+              e.key === "ArrowLeft" &&
+              e.target instanceof HTMLInputElement &&
+              !e.target.value &&
+              overflowBadgeRef.current
+            ) {
+              overflowBadgeRef.current.focus();
+            }
+          }}
Evidence
The public prop contract explicitly documents Backspace-based focus transfer, but the only
input-level focus transfer handler added for interactive chips checks ArrowLeft only.

packages/core/src/components/Dropdown/Dropdown.types.ts[23-34]
packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[33-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`interactiveChips`’ contract says Backspace (and ArrowLeft) from the input should move focus to the last chip. Current code only checks `ArrowLeft`, so Backspace does not move focus as documented.

## Issue Context
This is part of the new accessibility-focused feature set (`interactiveChips`). The docs/type comments and behavior should match to avoid misleading consumers and to ensure keyboard-only navigation works.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[41-70]
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[43-46]
- packages/core/src/components/Dropdown/Dropdown.types.ts[28-34]

## Suggested fix
Add a `Backspace` branch alongside `ArrowLeft` in the wrapper `onKeyDown`:
- Only trigger when the event target is the input and the input is empty.
- Prevent default/back-prop as needed so Backspace does not trigger any other removal behavior.
- Move focus directly to the last visible chip (or to the overflow badge only as an intermediate step if required), ensuring the documented behavior holds in both overflow and non-overflow cases.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Menu callbacks inverted 🐞 Bug ≡ Correctness
Description
useDropdownCombobox (and the multi variant) fires onMenuClose when isOpen becomes true and
onMenuOpen when it becomes false, reversing the intended lifecycle callbacks. Consumers relying on
these events will run side-effects on the wrong transition.
Code

packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[R67-74]

  onIsOpenChange: ({ isOpen }) => {
+      // Reset the text filter when the menu closes so reopening always shows the full option list,
+      // even though the input keeps displaying the selected item's label.
+      if (!isOpen) {
+        filterOptions("");
+      }
    isOpen ? onMenuClose?.() : onMenuOpen?.();
  },
Evidence
Both combobox hooks contain an inverted ternary that calls the wrong callback for the new isOpen
value; other dropdown hooks in the same codebase show the correct open/close mapping.

packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[67-74]
packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[89-93]
packages/core/src/components/Dropdown/hooks/useDropdownSelect.ts[67-69]
packages/core/src/components/Dropdown/hooks/useDropdownMultiSelect.ts[80-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The combobox hooks call `onMenuClose` on open and `onMenuOpen` on close (inverted), which breaks the semantic contract of these props.
## Issue Context
The select-based hooks already implement the correct mapping (`isOpen ? onMenuOpen : onMenuClose`), so the combobox hooks should match.
## Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[67-74]
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[89-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Empty inputValue overridden 🐞 Bug ≡ Correctness
Description
useDropdownCombobox seeds initialInputValue with inputValueProp || selectedItem?.label, so a
controlled empty string (inputValue="") is ignored and replaced by the selected label. This breaks
controlled input behavior for consumers that intentionally set an empty input value on mount.
Code

packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[R60-62]

+    // Seed the input with the selected item's label so a defaultValue/value is visible (and exposed to
+    // assistive technologies) on mount, now that the selection lives inside the input rather than in an overlay.
+    initialInputValue: inputValueProp || selectedItem?.label || "",
Evidence
The hook uses || when seeding initialInputValue, which collapses "" into the fallback; the
public props explicitly allow inputValue?: string, and the multi-combobox implementation uses ??
to preserve empty strings.

packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[60-63]
packages/core/src/components/Dropdown/Dropdown.types.ts[206-209]
packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[85-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`initialInputValue` uses `||`, which treats an empty string as falsy and incorrectly falls back to `selectedItem?.label`.
## Issue Context
`inputValue` is a `string` prop (empty string is valid). The multi-combobox hook already uses `??` for this exact pattern.
## Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[60-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Stale textInput summary ✓ Resolved 🐞 Bug ≡ Correctness
Description
In multi-select textInput mode, the input’s displayed summary is only seeded via
initialInputValue and updated on internal Downshift events; when value is controlled and the
parent changes value externally, there is no synchronization path to update Downshift’s
inputValue, so the input can display a stale summary that no longer matches the actual selection.
Code

packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[R87-88]

+    initialInputValue: inputValueProp ?? (textInput ? currentSelectedItems.map(i => i.label).join(", ") : ""),
id,
Evidence
The hook supports controlled selection via value?: Item[], but the textInput summary is only
applied through initialInputValue (initialization-only) and reducer branches tied to internal
events; no code updates Downshift’s inputValue when value changes externally, and the top-level
Dropdown does not remount controllers on value changes.

packages/core/src/components/Dropdown/Dropdown.types.ts[10-55]
packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[28-92]
packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[140-182]
packages/core/src/components/Dropdown/Dropdown.tsx[14-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
For multi-select `textInput`, the input shows a comma-separated summary derived from selected items. But `useCombobox` is only initialized with that summary via `initialInputValue`, and there is no effect or controlled `inputValue` prop to keep it in sync when `value` (controlled selected items) changes outside of Downshift interactions.
### Issue Context
This causes UI divergence in common flows like form reset, server-driven updates, or parent state changes: `selectedItems` in context updates (because it uses `value`), but the input continues to show the old summary.
### Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[28-118]
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[140-182]
### Suggested fix
Implement synchronization in `textInput` mode, for example:
- Maintain a dedicated `displayInputValue` state in the hook and pass it to `useCombobox` as `inputValue` (controlled), updating it on:
- user typing (InputChange)
- selection changes (when `currentSelectedItems` changes)
- close/blur (restore summary)
- Or, if available in your Downshift hook return, call `setInputValue(summary)` in a `useEffect` when `textInput` is true and `currentSelectedItems` changes, guarded so it doesn’t overwrite active user typing (e.g., only when menu is closed / input not focused).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Unused hook parameter 🐞 Bug ⚙ Maintainability
Description
useDropdownMultiCombobox adds an interactiveChips parameter but never uses it, which violates the
core package’s TypeScript unused-vars ESLint rule and can fail CI/linting. This also indicates the
hook signature doesn’t match its implemented behavior.
Code

packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[R23-26]

+  id?: string,
+  onOptionRemove?: (option: T) => void,
+  textInput?: boolean,
+  interactiveChips?: boolean
Evidence
The hook signature includes interactiveChips but the implementation’s control flow only uses
textInput (e.g., enableToggle = textInput), leaving interactiveChips unused. Core ESLint
config marks unused variables/args as an error for TS/TSX files, so this is a merge-blocking lint
issue when lint runs.

packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[7-36]
packages/core/.eslintrc.cjs[97-113]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useDropdownMultiCombobox` declares `interactiveChips?: boolean` but never references it. In `packages/core`, `@typescript-eslint/no-unused-vars` is configured as an error for TS/TSX files, so this can break lint/CI.
### Issue Context
`interactiveChips` is already consumed in UI (Trigger) and context; it does not appear to be needed inside this hook. The cleanest fix is to remove the argument from the hook signature and from the call site(s). If you want to keep it for future work, rename it to `_interactiveChips` to satisfy the lint rule.
### Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[7-36]
- packages/core/src/components/Dropdown/modes/DropdownMultiComboboxController.tsx[47-84]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Chip focus fails without overflow 🐞 Bug ≡ Correctness
Description
In interactiveChips mode, ArrowLeft from the input only focuses the overflow (+N) badge; when
there is no overflow badge (all chips visible), ArrowLeft does nothing so keyboard users can’t reach
chips via the documented navigation. This contradicts the interactiveChips prop contract that
ArrowLeft/Backspace from the input moves focus to the last chip.
Code

packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[R46-57]

+        <div
+          className={styles.multiWrapper}
+          onKeyDown={e => {
+            if (
+              e.key === "ArrowLeft" &&
+              e.target instanceof HTMLInputElement &&
+              !e.target.value &&
+              overflowBadgeRef.current
+            ) {
+              overflowBadgeRef.current.focus();
+            }
+          }}
Evidence
The code explicitly gates focus transfer on the presence of overflowBadgeRef.current, and the only
other ArrowLeft focus transfer logic lives on the overflow-counter wrapper that is rendered only
when hiddenCount > 0. The prop’s own documentation states ArrowLeft/Backspace should move focus to
the last chip, which isn’t satisfied when there’s no overflow.

packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[41-57]
packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[129-173]
packages/core/src/components/Dropdown/Dropdown.types.ts[29-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`interactiveChips` documents that ArrowLeft/Backspace from the input moves focus to the last chip, but the current implementation only moves focus to the overflow badge when it exists. When `hiddenCount===0` (no overflow), there is no focus target and the keyboard flow breaks.
### Issue Context
- The ArrowLeft handler in `MultiSelectTrigger` only focuses `overflowBadgeRef`.
- The ArrowLeft-to-chip behavior in `MultiSelectedValues` is implemented only on the overflow wrapper, which only exists when `hiddenCount > 0`.
### Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[41-71]
- packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[129-173]
- packages/core/src/components/Dropdown/Dropdown.types.ts[29-34]
### Implementation guidance
- Add a ref to the last *visible* chip container and focus it when ArrowLeft is pressed from an empty input and there is no overflow badge.
- Example approach:
- In `MultiSelectTrigger`, create `const lastChipRef = useRef<HTMLDivElement>(null)`.
- Pass it via `getChipContainerProps` for the last selected chip (only when you know there is no overflow) and ensure the chip container is focusable.
- Update the ArrowLeft handler:
- If `overflowBadgeRef.current` exists, focus it (current behavior).
- Else `lastChipRef.current?.focus()`.
- Consider also handling `Backspace` similarly if that’s part of the intended keyboard contract for `interactiveChips`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

10. Dangling hasSelected class 🐞 Bug ≡ Correctness ⭐ New
Description
Trigger.module.scss removes the .hasSelected rule but DropdownInput still conditionally adds
styles.hasSelected; when that condition becomes true, classnames can emit a literal "undefined"
class and the intended styling will never apply. This is reachable when selectedItem exists but
the user clears the input text (inputValue becomes empty).
Code

packages/core/src/components/Dropdown/components/Trigger/Trigger.module.scss[L49-53]

-    &.hasSelected {
-      position: absolute;
-      inset: 0;
-      cursor: pointer;
-    }
Evidence
DropdownInput still references styles.hasSelected, but the SCSS module no longer defines it
under .inputWrapper, so the generated class key becomes undefined at runtime when the condition is
true.

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[48-71]
packages/core/src/components/Dropdown/components/Trigger/Trigger.module.scss[28-48]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The SCSS module no longer defines `.hasSelected`, but the TSX still references `styles.hasSelected`. With CSS modules, `styles.hasSelected` becomes `undefined`, so when the condition is true the rendered class list can contain the string `"undefined"` and any prior positioning/styling is lost.

## Issue Context
This was likely left over from the previous “selected overlay” behavior. Even if the condition is rarer now, it is still reachable (e.g., selectedItem present + user clears the input), and it’s a fragile footgun.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[60-70]
- packages/core/src/components/Dropdown/components/Trigger/Trigger.module.scss[28-48]

## Suggested fix
Choose one:
1) Remove the `[styles.hasSelected]: ...` conditional entirely if it’s no longer needed.
2) Reintroduce a `.hasSelected` rule (or rename to an existing class) if the styling is still required for some state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Wrong close detection 🐞 Bug ≡ Correctness
Description
In useDropdownMultiCombobox the stateReducer treats changes.isOpen being undefined the same
as false via !changes.isOpen, so non-close transitions that omit isOpen can incorrectly force
inputValue back to closedInputValue while the menu is still open.
Code

packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[R178-180]

+          if (!changes.isOpen && state.isOpen) {
+            return { ...changes, inputValue: closedInputValue };
+          }
Evidence
The reducer’s default branch uses a falsy check on changes.isOpen, which will also match
undefined, and then overwrites inputValue with closedInputValue even though state.isOpen is
true.

packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[140-182]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`useDropdownMultiCombobox` uses a falsy check (`!changes.isOpen`) to infer that the menu is closing. Since `changes.isOpen` can be `undefined` when Downshift doesn’t include it in `changes`, the reducer can misclassify state updates as a close and overwrite `inputValue` unexpectedly.
### Issue Context
This logic runs in the new multi-combobox `stateReducer` default branch and affects both default multi-select and the new `textInput` mode.
### Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[140-182]
### Suggested fix
Use a strict close check, e.g. `if (changes.isOpen === false && state.isOpen) { ... }`, instead of a falsy check. If you also need to handle explicit open transitions, handle `changes.isOpen === true` separately.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Object refs dropped 🐞 Bug ☼ Reliability
Description
MultiSelectedValues merges refs for chip containers but only forwards function refs from
getChipContainerProps; if a RefObject is provided, it never receives the element, breaking
external focus management that relies on object refs.
Code

packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[R82-91]

+      const extraProps = isVisible && getChipContainerProps ? getChipContainerProps(item, index) : {};
+      const { ref: extraRef, ...extraAttrs } = extraProps;
  return (
    <div
      key={`dropdown-chip-visible-${item.value}`}
-          ref={itemRefs[index]}
+          ref={el => {
+            (itemRefs[index] as React.MutableRefObject<HTMLDivElement | null>).current = el;
+            if (typeof extraRef === "function") extraRef(el);
+          }}
Evidence
The component destructures ref out of the extra props and only invokes it when it’s a function, so
object refs are silently ignored.

packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[13-24]
packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[79-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MultiSelectedValues` ref-merging only handles callback refs (`typeof extraRef === "function"`). If callers provide a `RefObject` (e.g. `useRef()` / `createRef()`), it will be ignored and never populated.
### Issue Context
`getChipContainerProps` is typed as returning a generic props object and can reasonably include either callback refs or object refs.
### Fix Focus Areas
- packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[79-110]
### Suggested fix
Update the ref callback to handle both shapes:
- If `extraRef` is a function: `extraRef(el)`
- Else if `extraRef` is an object ref: `(extraRef as React.MutableRefObject<HTMLDivElement | null>).current = el`
Also consider passing `null` on unmount (`el === null`) for both ref types.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (5)
13. onKeyDown prop overridden 🐞 Bug ≡ Correctness
Description
DropdownInput sets onKeyDown: externalKeyDown but then spreads multipleSelectionDropdownProps
afterward, so any colliding keys (notably onKeyDown) can overwrite the external handler. This
makes the new onKeyDown prop unreliable when multi-selection keyboard props are present.
Code

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[R45-51]

+  const preventKeyAction = interactiveChips ? !!(inputValue && inputValue.length > 0) : isOpen;
+  const multipleSelectionDropdownProps = getDropdownProps ? getDropdownProps({ preventKeyAction }) : {};
-  return (
+return (
<>
{searchable ? (
<BaseInput
Evidence
The code explicitly assigns onKeyDown: externalKeyDown and then applies
...multipleSelectionDropdownProps afterward, which will override earlier properties on key
collisions. The same block shows multipleSelectionDropdownProps is derived from
getDropdownProps, so it is intended to contribute keyboard handlers.

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[40-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In `DropdownInput`, the object passed to `getInputProps` sets `onKeyDown: externalKeyDown` and then spreads `...multipleSelectionDropdownProps` after it. If `multipleSelectionDropdownProps` contains an `onKeyDown`, it will overwrite `externalKeyDown`, so the newly added external handler may never run.
### Issue Context
`multipleSelectionDropdownProps` comes from `getDropdownProps({ preventKeyAction })` and is specifically used for keyboard behavior; it is a realistic source of an `onKeyDown` collision.
### Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[42-60]
### Suggested fix approach
- Extract `multipleSelectionDropdownProps.onKeyDown` and compose it with `externalKeyDown` (call both, preserving existing behavior).
- Alternatively, spread `multipleSelectionDropdownProps` *before* setting `onKeyDown`, and set `onKeyDown` to a composed handler that invokes both.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. interactiveChips lacks test coverage 📘 Rule violation ☼ Reliability
Description
The PR introduces interactiveChips keyboard-focus behavior (ArrowLeft focus transfer and chip
focus management) but adds no tests validating the new interaction and related accessibility
behavior. This increases regression risk for keyboard-only users and makes the a11y-focused behavior
change hard to verify.
Code

packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[R41-72]

+    if (interactiveChips && searchable && !readOnly) {
+      if (selectedItems.length === 0) {
+        return <DropdownInput />;
+      }
+      return (
+        <div
+          className={styles.multiWrapper}
+          onKeyDown={e => {
+            if (
+              e.key === "ArrowLeft" &&
+              e.target instanceof HTMLInputElement &&
+              !e.target.value &&
+              overflowBadgeRef.current
+            ) {
+              overflowBadgeRef.current.focus();
+            }
+          }}
+        >
+          <MultiSelectedValues
+            disabled={disabled}
+            readOnly={readOnly}
+            selectedItems={selectedItems}
+            onRemove={item => contextOnOptionRemove?.(item)}
+            renderInput={() => <DropdownInput inputSize="small" fullWidth />}
+            getChipContainerProps={(item, index) =>
+              getSelectedItemProps?.({ selectedItem: item, index }) ?? {}
+            }
+            badgeRef={overflowBadgeRef}
+            minVisibleCount={minVisibleCount}
+          />
+        </div>
+      );
Evidence
interactiveChips introduces new keyboard handling and focus management in the trigger and overflow
counter, but the updated test suite shown focuses on textInput multi-select behaviors and does not
include any interactiveChips scenarios validating focus movement, keyboard navigation, or removal
behavior.

CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests Must Validate Real Behavior and Accessibility Attributes: CLAUDE.md: Tests...

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Fix Dropdown a11y: keep selected label in input for searchable single select
🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

Walkthroughs

Description
• Keep searchable single-select selection text in the input for WCAG 4.1.2 compliance.
• Add multi-select a11y modes: text summary in input or keyboard-navigable chips.
• Expand Storybook docs and update tests for new value-rendering behavior.
Diagram
graph TD
  A["Dropdown"] --> B("useDropdownCombobox") --> D["DropdownContext"]
  A --> C("useDropdownMultiCombobox") --> D
  D --> E["SingleSelectTrigger"]
  D --> F["MultiSelectTrigger"] --> G["MultiSelectedValues"]
  A --> H[["Storybook docs"]]

  subgraph Legend
    direction LR
    _cmp["UI component"] ~~~ _hook("Hook") ~~~ _docs[["Docs/Stories"]]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep overlay, sync an offscreen value for AT
  • ➕ Preserves rich selected-value rendering (icons/avatars/valueRenderer) while still exposing text to AT
  • ➕ Potentially minimal change to Downshift stateReducer behavior
  • ➖ Higher complexity: must keep overlay, hidden text, and input semantics in sync
  • ➖ Greater risk of divergence across focus/blur/open/close paths and screen reader quirks
2. Use aria-valuetext (or similar) instead of input value
  • ➕ Leaves visual/interaction model unchanged
  • ➕ Avoids changing Downshift’s default inputValue behavior
  • ➖ Inconsistent support/behavior across AT + browser combinations for combobox patterns
  • ➖ Still leaves the actual input value empty, which can confuse tooling and some AT announcements
3. Split into two PRs (single-select fix first, multi-select a11y modes second)
  • ➕ Reduces review surface area and isolates risk
  • ➕ Allows faster patch release for WCAG blocker
  • ➖ Delays shipping the multi-select accessibility improvements and accompanying docs
  • ➖ Some refactors (trigger/context changes) would be duplicated across PRs

Recommendation: Current approach is the most robust for WCAG 4.1.2 because it uses the native/programmatic source of truth (the input value) rather than a parallel overlay. Keeping Downshift’s default inputValue on selection aligns with common combobox implementations and reduces AT-specific edge cases. For multi-select, providing explicit modes (textInput vs interactiveChips) is a pragmatic, opt-in path that preserves existing chip UI while enabling accessible value announcement and keyboard removal; alternatives that rely on hidden/aria-only value mirrors are typically more fragile.

Grey Divider

File Changes

Enhancement (7)
Dropdown.types.ts Add multi-select accessibility mode props (textInput, interactiveChips) +12/-0

Add multi-select accessibility mode props (textInput, interactiveChips)

• Introduces new multi-select props to control how selections are exposed: a text summary inside the input (textInput) and always-visible, keyboard-navigable chips (interactiveChips). Documents intended WCAG 4.1.2 behavior and scope (searchable-only).

packages/core/src/components/Dropdown/Dropdown.types.ts


MultiSelectedValues.tsx Expose chip container props and overflow badge ref for keyboard focus management +20/-3

Expose chip container props and overflow badge ref for keyboard focus management

• Adds optional getChipContainerProps to spread per-chip attributes/refs (e.g., tabIndex, onKeyDown) and badgeRef to target the overflow “+N” chip. Enhances overflow badge key handling (ArrowLeft) to move focus to the last visible chip.

packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx


DropdownInput.tsx Allow external inputRef/onKeyDown and refine preventKeyAction for chip navigation +22/-6

Allow external inputRef/onKeyDown and refine preventKeyAction for chip navigation

• Extends DropdownInput to accept an external ref and keydown handler for higher-level focus/navigation control. Adjusts Downshift preventKeyAction behavior so interactiveChips can still use Backspace/arrow navigation when appropriate (suppresses only while input has typed text).

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx


MultiSelectTrigger.tsx Add multi-select trigger rendering modes: textInput and interactiveChips +87/-38

Add multi-select trigger rendering modes: textInput and interactiveChips

• Refactors trigger rendering into a mode switch: textInput (always render input with summary), interactiveChips (chips become keyboard-focusable with ArrowLeft from input into chips), and default chips mode (existing behavior). Wires getSelectedItemProps and overflow badge focus bridging.

packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx


DropdownContext.types.ts Add context surface for chip props and multi-select a11y modes +3/-0

Add context surface for chip props and multi-select a11y modes

• Extends Dropdown context types with getSelectedItemProps plus textInput and interactiveChips flags. Enables triggers/selected-value components to coordinate keyboard navigation and rendering mode.

packages/core/src/components/Dropdown/context/DropdownContext.types.ts


useDropdownMultiCombobox.ts Add textInput multi-select summary mode and interactive chip removal notifications +95/-14

Add textInput multi-select summary mode and interactive chip removal notifications

• Adds textInput and interactiveChips parameters to control whether the input shows a comma-separated selection summary and to support richer keyboard interactions. Implements toggle-on-repeat-click behavior for textInput using a pendingToggleRef and stateReducer changes. Ensures filter resets on open/close changes and onOptionRemove fires for keyboard-driven chip deletions.

packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts


DropdownMultiComboboxController.tsx Plumb textInput/interactiveChips props into multi-combobox hook and context +13/-4

Plumb textInput/interactiveChips props into multi-combobox hook and context

• Passes new props and onOptionRemove into useDropdownMultiCombobox and exposes getSelectedItemProps via context. Adds textInput/interactiveChips flags to the context value so triggers can select the correct rendering/navigation behavior.

packages/core/src/components/Dropdown/modes/DropdownMultiComboboxController.tsx


Bug fix (2)
SingleSelectTrigger.tsx Disable selected-value overlay for searchable single select +4/-12

Disable selected-value overlay for searchable single select

• Stops rendering the selected-item overlay when searchable=true, ensuring the selected label is shown via the input value instead. Keeps overlay behavior for non-searchable single select, preserving rich valueRenderer behavior on that path.

packages/core/src/components/Dropdown/components/Trigger/SingleSelectTrigger.tsx


useDropdownCombobox.ts Keep selected label as input value in searchable single-select combobox +17/-7

Keep selected label as input value in searchable single-select combobox

• Seeds initialInputValue from selectedItem.label so defaultValue/controlled value is visible on mount. Filters only on real InputChange events (ignores Downshift-driven value writes). Removes stateReducer overrides that forced inputValue to null; resets filter on menu close to show full list on reopen.

packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts


Refactor (1)
Trigger.module.scss Remove searchable selected-item overlay positioning/fade styles +0/-10

Remove searchable selected-item overlay positioning/fade styles

• Deletes CSS for the overlay’s absolute positioning and focused ‘faded’ state, which no longer applies to searchable single select. Retains base selectedItem styling for non-searchable overlay rendering.

packages/core/src/components/Dropdown/components/Trigger/Trigger.module.scss


Tests (1)
Dropdown.test.tsx Update Dropdown tests for input-value selection and textInput multi mode +73/-82

Update Dropdown tests for input-value selection and textInput multi mode

• Rewrites single-select searchable assertions to expect the selected label inside the input (no faded overlay). Adjusts indent/startElement overlay test to run only for non-searchable single select. Updates multiple multi-select tests to run in textInput mode and assert comma-separated input summaries and toggle/deselect behavior.

packages/core/src/components/Dropdown/tests/Dropdown.test.tsx


Documentation (4)
DropdownMultiSelectA11y.mdx Add multi-select accessibility reference page +46/-0

Add multi-select accessibility reference page

• Introduces MDX documentation explaining why chip-only selections fail WCAG 4.1.2 when the input is blank. Documents two supported solutions (textInput and interactiveChips) and includes embedded Storybook canvases for each, including overflow behavior.

packages/docs/src/pages/components/Dropdown/DropdownMultiSelectA11y.mdx


DropdownMultiSelectA11y.stories.tsx Add Storybook stories for multi-select textInput and interactiveChips +107/-0

Add Storybook stories for multi-select textInput and interactiveChips

• Adds three focused stories: textInput basic, interactiveChips basic, and interactiveChips overflow with minVisibleCount. Stories are instrumented with action logging for selection/removal/clear callbacks.

packages/docs/src/pages/components/Dropdown/DropdownMultiSelectA11y.stories.tsx


DropdownSearchableSingleSelect.mdx Document searchable single-select accessibility behavior and trade-offs +106/-0

Document searchable single-select accessibility behavior and trade-offs

• Adds an accessibility reference explaining the prior overlay behavior, the WCAG 4.1.2 issue, and the new model where the selected label is inside the input. Calls out trade-offs (collapsed value is text-only) and documents relevant naming/state props and best practices (e.g., clearAriaLabel, placeholder is not a label).

packages/docs/src/pages/components/Dropdown/DropdownSearchableSingleSelect.mdx


DropdownSearchableSingleSelect.stories.tsx Add comprehensive Searchable single select Storybook page +564/-0

Add comprehensive Searchable single select Storybook page

• Creates a dedicated story set covering searchable single-select variants: overview, sizes, states, default/controlled value, icons/avatars, end elements, valueRenderer behavior, groups, tooltips, clearable/max-height, and custom filtering/empty state. Includes examples that demonstrate the selected label living inside the input on mount and after selection.

packages/docs/src/pages/components/Dropdown/DropdownSearchableSingleSelect.stories.tsx


Grey Divider

Qodo Logo

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Analysis

✅ No bundle size changes detected.

Unchanged Components
Component Base PR Diff
@vibe/button 17.31KB 17.28KB -36B 🟢
@vibe/clickable 5.97KB 5.97KB +8B 🔺
@vibe/dialog 52.19KB 52.18KB -13B 🟢
@vibe/icon-button 66.25KB 66.08KB -176B 🟢
@vibe/icon 12.93KB 12.87KB -59B 🟢
@vibe/layer 2.97KB 2.96KB -10B 🟢
@vibe/layout 9.86KB 9.82KB -43B 🟢
@vibe/loader 5.67KB 5.65KB -28B 🟢
@vibe/tooltip 61.34KB 61.29KB -47B 🟢
@vibe/typography 63.45KB 63.46KB +5B 🔺
Accordion 6.3KB 6.29KB -12B 🟢
AccordionItem 66.5KB 66.46KB -41B 🟢
AlertBanner 70.91KB 70.81KB -106B 🟢
AlertBannerButton 18.79KB 18.78KB -16B 🟢
AlertBannerLink 15.24KB 15.25KB +10B 🔺
AlertBannerText 64.03KB 63.93KB -100B 🟢
AttentionBox 74.42KB 74.28KB -142B 🟢
Avatar 66.8KB 66.77KB -24B 🟢
AvatarGroup 93.35KB 93.33KB -19B 🟢
Badge 43.18KB 43.21KB +33B 🔺
BreadcrumbItem 64.74KB 64.72KB -20B 🟢
BreadcrumbMenu 68.64KB 68.62KB -23B 🟢
BreadcrumbMenuItem 77.07KB 77.04KB -32B 🟢
BreadcrumbsBar 5.68KB 5.69KB +8B 🔺
ButtonGroup 68.41KB 68.39KB -28B 🟢
Checkbox 66.95KB 66.94KB -16B 🟢
Chips 75.13KB 75.03KB -98B 🟢
ColorPicker 74.53KB 74.43KB -104B 🟢
ColorPickerContent 73.75KB 73.83KB +78B 🔺
Combobox 84.08KB 84.03KB -55B 🟢
Counter 42.21KB 42.23KB +14B 🔺
DatePicker 112.45KB 112.85KB +413B 🔺
Divider 5.45KB 5.44KB -10B 🟢
Dropdown 95.33KB 95.76KB +442B 🔺
EditableHeading 66.59KB 66.65KB +60B 🔺
EditableText 66.49KB 66.54KB +46B 🔺
EmptyState 70.43KB 70.42KB -7B 🟢
ExpandCollapse 66.28KB 66.24KB -37B 🟢
FormattedNumber 5.83KB 5.84KB +7B 🔺
GridKeyboardNavigationContext 4.66KB 4.65KB -16B 🟢
HiddenText 5.42KB 5.39KB -35B 🟢
Info 72.15KB 72.06KB -89B 🟢
Label 68.65KB 68.67KB +15B 🔺
Link 14.92KB 14.88KB -40B 🟢
List 72.92KB 72.87KB -55B 🟢
ListItem 65.54KB 65.53KB -10B 🟢
ListItemAvatar 66.9KB 66.92KB +21B 🔺
ListItemIcon 13.97KB 14.02KB +54B 🔺
ListTitle 65.08KB 65.06KB -25B 🟢
Menu 8.63KB 8.65KB +17B 🔺
MenuDivider 5.55KB 5.56KB +3B 🔺
MenuGridItem 7.21KB 7.19KB -12B 🟢
MenuItem 76.99KB 76.97KB -17B 🟢
MenuItemButton 70.02KB 70.15KB +135B 🔺
MenuTitle 65.36KB 65.27KB -84B 🟢
MenuButton 66.14KB 66.21KB +71B 🔺
Modal 79.2KB 79.13KB -78B 🟢
ModalContent 4.73KB 4.71KB -12B 🟢
ModalHeader 65.88KB 65.79KB -96B 🟢
ModalMedia 7.53KB 7.47KB -56B 🟢
ModalFooter 67.75KB 67.7KB -52B 🟢
ModalFooterWizard 68.61KB 68.61KB 0B ➖
ModalBasicLayout 8.92KB 8.94KB +15B 🔺
ModalMediaLayout 8.08KB 8.06KB -18B 🟢
ModalSideBySideLayout 6.3KB 6.31KB +13B 🔺
MultiStepIndicator 53KB 52.93KB -80B 🟢
NumberField 72.84KB 72.85KB +14B 🔺
ProgressBar 7.37KB 7.34KB -39B 🟢
RadioButton 65.88KB 65.95KB +69B 🔺
Search 70.57KB 70.71KB +136B 🔺
Skeleton 6.02KB 6.01KB -8B 🟢
Slider 73.95KB 73.81KB -147B 🟢
SplitButton 66.49KB 66.53KB +38B 🔺
SplitButtonMenu 8.76KB 8.78KB +17B 🔺
Steps 71.39KB 71.34KB -56B 🟢
Table 7.25KB 7.24KB -14B 🟢
TableBody 66.77KB 66.78KB +10B 🔺
TableCell 65.28KB 65.19KB -96B 🟢
TableContainer 5.34KB 5.32KB -24B 🟢
TableHeader 5.66KB 5.63KB -25B 🟢
TableHeaderCell 72.25KB 72.17KB -78B 🟢
TableRow 5.56KB 5.54KB -13B 🟢
TableRowMenu 68.89KB 68.8KB -87B 🟢
TableVirtualizedBody 71.49KB 71.43KB -67B 🟢
Tab 64.04KB 64.05KB +9B 🔺
TabList 8.91KB 8.85KB -64B 🟢
TabPanel 5.3KB 5.29KB -12B 🟢
TabPanels 5.85KB 5.84KB -13B 🟢
TabsContext 5.47KB 5.51KB +33B 🔺
TextArea 66.26KB 66.39KB +125B 🔺
TextField 69.44KB 69.4KB -46B 🟢
TextWithHighlight 64.3KB 64.26KB -44B 🟢
ThemeProvider 4.37KB 4.36KB -8B 🟢
Tipseen 71.2KB 71.1KB -98B 🟢
TipseenContent 71.63KB 71.71KB +82B 🔺
TipseenMedia 71.37KB 71.38KB +13B 🔺
TipseenWizard 73.86KB 73.86KB +8B 🔺
Toast 74.06KB 74.08KB +17B 🔺
ToastButton 18.64KB 18.61KB -33B 🟢
ToastLink 15.08KB 15.09KB +15B 🔺
Toggle 66.62KB 66.64KB +19B 🔺
TransitionView 5.44KB 5.44KB -1B 🟢
VirtualizedGrid 12.53KB 12.53KB +3B 🔺
VirtualizedList 12.26KB 12.28KB +20B 🔺
List (Next) 8.2KB 8.15KB -48B 🟢
ListItem (Next) 69.97KB 69.94KB -38B 🟢
ListTitle (Next) 65.33KB 65.34KB +14B 🔺

📊 Summary:

  • Total Base Size: 4.76MB
  • Total PR Size: 4.75MB
  • Total Difference: 1KB

Comprehensive story page covering all searchable single-select variants:
overview, sizes, states, default/controlled value, icons & avatars, groups,
tooltips, clearable/max-height, and custom filter / empty message. The
default-value story demonstrates the selected value living inside the input.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 6edf9ba

Single-page accessibility reference covering the selected-value-in-input
behavior, WCAG criteria, keyboard interaction, screen reader output, and the
accessibility-relevant props (naming, state, feedback). Excludes layout props
like size that do not affect accessibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 3417b82

Convert the plain .md (which Storybook does not pick up) into an .mdx docs
page for the Searchable single select group, with the live examples embedded.
Trim to accessibility essentials: core selected-value-in-input behavior,
WCAG 4.1.2, and the accessibility-relevant props. Dropped the generic keyboard
and screen-reader sections and the broader WCAG list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 11b7f86

…single select

The .selectedItem overlay was previously hidden for searchable mode only via
the `!inputValue` guard, so it reappeared and coexisted with the input value
whenever the input text was cleared while a selection remained. Gate the
overlay on `!searchable` instead — for searchable single select the value
lives inside the input, so the overlay must never render. Removes the now-dead
`faded`/`hasSelected` classes and their styles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 127c22d

…-off examples

Add a clear "What changed" before/after section to the searchable single
select accessibility page, and stories that demonstrate the text-only
collapsed selected value: preselected start elements (icons/avatars), end
elements (trailing icon, suffix/hint), and a custom valueRenderer that is not
applied to the searchable selected display. Remove the Do/Don't section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit d0ea70b

MDX parses {curly braces} as JS expressions; "{selected label}" is not valid
JS and broke the Storybook/Chromatic preview build with an acorn parse error.
Replace it with plain text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Looking for bugs?

Check back in a few minutes. An AI review agent is analyzing this pull request.

rivka-ungar and others added 3 commits June 12, 2026 01:01
textInput: selected items are shown as a comma-separated summary in the
input (WCAG 4.1.2 — exposes value to assistive technologies on focus).

interactiveChips: chips stay visible alongside the input; keyboard nav
via getSelectedItemProps — ArrowLeft/Right moves between chips,
Backspace/Delete removes the focused chip, ArrowLeft from an empty input
navigates to the last chip or the +N overflow badge. Chips overflow uses
the existing useItemsOverflow hook for the +N count badge.

Both modes keep the menu open on selection and support toggle (re-click
to deselect). Default chip mode behavior is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Storybook MDX page and stories for the textInput and interactiveChips
props explaining how each addresses the WCAG 4.1.2 gap in default
multi-select (empty input announced as blank by screen readers).

Covers:
- Side-by-side comparison of all three modes
- textInput: comma-separated value in the input, exposed on mount,
  controlled usage, trade-offs
- interactiveChips: keyboard navigation table, overflow (+N badge),
  trade-offs
- Decision guide for choosing between modes
- Common a11y props table (label, clearAriaLabel, error, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 4fcb1dc

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 1769c1d

Comment on lines +46 to +57
<div
className={styles.multiWrapper}
onKeyDown={e => {
if (
e.key === "ArrowLeft" &&
e.target instanceof HTMLInputElement &&
!e.target.value &&
overflowBadgeRef.current
) {
overflowBadgeRef.current.focus();
}
}}

This comment was marked as resolved.

…ipleSelection onStateChange

UseMultipleSelectionStateChange has no selectedItem property — diff old vs new selectedItems array instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit f279286

@github-actions

Copy link
Copy Markdown
Contributor

A new prerelease version of this PR has been published! 🎉
To use this prerelease version, install the needed packages in your project:

@vibe/core@4.4.0-alpha-26200.0
@vibe/docs@4.0.11-alpha-26200.0

Comment on lines +87 to 88
initialInputValue: inputValueProp ?? (textInput ? currentSelectedItems.map(i => i.label).join(", ") : ""),
id,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Stale textinput summary 🐞 Bug ≡ Correctness

In multi-select textInput mode, the input’s displayed summary is only seeded via
initialInputValue and updated on internal Downshift events; when value is controlled and the
parent changes value externally, there is no synchronization path to update Downshift’s
inputValue, so the input can display a stale summary that no longer matches the actual selection.
Agent Prompt
### Issue description
For multi-select `textInput`, the input shows a comma-separated summary derived from selected items. But `useCombobox` is only initialized with that summary via `initialInputValue`, and there is no effect or controlled `inputValue` prop to keep it in sync when `value` (controlled selected items) changes outside of Downshift interactions.

### Issue Context
This causes UI divergence in common flows like form reset, server-driven updates, or parent state changes: `selectedItems` in context updates (because it uses `value`), but the input continues to show the old summary.

### Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[28-118]
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[140-182]

### Suggested fix
Implement synchronization in `textInput` mode, for example:
- Maintain a dedicated `displayInputValue` state in the hook and pass it to `useCombobox` as `inputValue` (controlled), updating it on:
  - user typing (InputChange)
  - selection changes (when `currentSelectedItems` changes)
  - close/blur (restore summary)
- Or, if available in your Downshift hook return, call `setInputValue(summary)` in a `useEffect` when `textInput` is true and `currentSelectedItems` changes, guarded so it doesn’t overwrite active user typing (e.g., only when menu is closed / input not focused).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

…ow example

State the major issue prominently (selected chips live outside the input, so a
multi-select with selections is announced as "blank" — failing WCAG 4.1.2), and
add an InteractiveChipsOverflow example showing chips collapsing into a "+N"
counter via minVisibleCount.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 18df1ce

Comment on lines 67 to 74
onIsOpenChange: ({ isOpen }) => {
// Reset the text filter when the menu closes so reopening always shows the full option list,
// even though the input keeps displaying the selected item's label.
if (!isOpen) {
filterOptions("");
}
isOpen ? onMenuClose?.() : onMenuOpen?.();
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Menu callbacks inverted 🐞 Bug ≡ Correctness

useDropdownCombobox (and the multi variant) fires onMenuClose when isOpen becomes true and
onMenuOpen when it becomes false, reversing the intended lifecycle callbacks. Consumers relying on
these events will run side-effects on the wrong transition.
Agent Prompt
## Issue description
The combobox hooks call `onMenuClose` on open and `onMenuOpen` on close (inverted), which breaks the semantic contract of these props.

## Issue Context
The select-based hooks already implement the correct mapping (`isOpen ? onMenuOpen : onMenuClose`), so the combobox hooks should match.

## Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[67-74]
- packages/core/src/components/Dropdown/hooks/useDropdownMultiCombobox.ts[89-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +60 to +62
// Seed the input with the selected item's label so a defaultValue/value is visible (and exposed to
// assistive technologies) on mount, now that the selection lives inside the input rather than in an overlay.
initialInputValue: inputValueProp || selectedItem?.label || "",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Empty inputvalue overridden 🐞 Bug ≡ Correctness

useDropdownCombobox seeds initialInputValue with inputValueProp || selectedItem?.label, so a
controlled empty string (inputValue="") is ignored and replaced by the selected label. This breaks
controlled input behavior for consumers that intentionally set an empty input value on mount.
Agent Prompt
## Issue description
`initialInputValue` uses `||`, which treats an empty string as falsy and incorrectly falls back to `selectedItem?.label`.

## Issue Context
`inputValue` is a `string` prop (empty string is valid). The multi-combobox hook already uses `??` for this exact pattern.

## Fix Focus Areas
- packages/core/src/components/Dropdown/hooks/useDropdownCombobox.ts[60-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

…Trigger formatting

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 30a6147

Comment on lines +9 to +19
const DropdownInput = ({
inputSize,
fullWidth,
onKeyDown: externalKeyDown,
inputRef: externalInputRef
}: {
inputSize?: "small" | "medium" | "large";
fullWidth?: boolean;
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
inputRef?: RefObject<HTMLInputElement>;
}) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. dropdowninput props typed inline 📘 Rule violation ⚙ Maintainability

DropdownInput defines its props type inline in the component file and does not extend
VibeComponentProps. This breaks the repo’s required prop typing convention and makes the component
API harder to standardize and reuse.
Agent Prompt
## Issue description
`DropdownInput` has an inline props type in `DropdownInput.tsx` and it does not extend `VibeComponentProps`, which violates the component props typing convention.

## Issue Context
The PR adds new props (`onKeyDown`, `inputRef`) and types them inline, further expanding the component API without using the required `*.types.ts` pattern.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-19]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 17 to 24
disabled?: boolean;
readOnly?: boolean;
minVisibleCount?: number;
/** Extra props (tabIndex, onKeyDown, etc.) to spread on each visible chip container. */
getChipContainerProps?: (item: Item, index: number) => Record<string, any>;
/** Ref forwarded to the +N overflow Chips element, for external keyboard focus management. */
badgeRef?: React.Ref<HTMLDivElement>;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. multiselectedvaluesprops typed inline 📘 Rule violation ⚙ Maintainability

MultiSelectedValues keeps its props type (MultiSelectedValuesProps) defined inside the component
file and it does not extend VibeComponentProps. This violates the required component props typing
standard.
Agent Prompt
## Issue description
`MultiSelectedValuesProps` is defined inline in the component file and does not extend `VibeComponentProps`, contrary to the required typing convention.

## Issue Context
This PR expands the props surface area (e.g., `getChipContainerProps`, `badgeRef`) while keeping the props type local to the `.tsx` file.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx[13-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +9 to +19
const DropdownInput = ({
inputSize,
fullWidth,
onKeyDown: externalKeyDown,
inputRef: externalInputRef
}: {
inputSize?: "small" | "medium" | "large";
fullWidth?: boolean;
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
inputRef?: RefObject<HTMLInputElement>;
}) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. dropdowninput not forwardref 📘 Rule violation ⚙ Maintainability

DropdownInput is implemented as a plain function component and introduces an inputRef prop
instead of using React.forwardRef. This violates the required forwardRef component pattern and can
lead to inconsistent ref support across the design system.
Agent Prompt
## Issue description
`DropdownInput` does not use `React.forwardRef` and instead adds an `inputRef` prop, which conflicts with the compliance requirement that components use the forwardRef pattern.

## Issue Context
The component now accepts `inputRef?: RefObject<HTMLInputElement>` and manually chooses between internal vs external refs, but it still doesn’t expose a standard `ref` to consumers.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-19]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +46 to +57
<div
className={styles.multiWrapper}
onKeyDown={e => {
if (
e.key === "ArrowLeft" &&
e.target instanceof HTMLInputElement &&
!e.target.value &&
overflowBadgeRef.current
) {
overflowBadgeRef.current.focus();
}
}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. Backspace chip-nav missing 🐞 Bug ≡ Correctness

The interactiveChips prop documentation promises that pressing Backspace from the input moves
focus to the last chip, but the implementation only handles ArrowLeft and never handles
Backspace. As a result, keyboard-only users can’t perform the documented navigation in
interactiveChips mode.
Agent Prompt
## Issue description
`interactiveChips`’ contract says Backspace (and ArrowLeft) from the input should move focus to the last chip. Current code only checks `ArrowLeft`, so Backspace does not move focus as documented.

## Issue Context
This is part of the new accessibility-focused feature set (`interactiveChips`). The docs/type comments and behavior should match to avoid misleading consumers and to ensure keyboard-only navigation works.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/MultiSelectTrigger.tsx[41-70]
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[43-46]
- packages/core/src/components/Dropdown/Dropdown.types.ts[28-34]

## Suggested fix
Add a `Backspace` branch alongside `ArrowLeft` in the wrapper `onKeyDown`:
- Only trigger when the event target is the input and the input is empty.
- Prevent default/back-prop as needed so Backspace does not trigger any other removal behavior.
- Move focus directly to the last visible chip (or to the overflow badge only as an intermediate step if required), ensuring the documented behavior holds in both overflow and non-overflow cases.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant